﻿/*
COPYRIGHT (C) 2009 EPISERVER AB

THIS FILE IS PART OF REMOTE PAGE PROVIDER FOR EPISERVER CMS.
REMOTE PAGE PROVIDER FOR EPISERVER CMS IS FREE SOFTWARE: YOU CAN REDISTRIBUTE IT AND/OR MODIFY IT UNDER THE TERMS OF THE GNU LESSER GENERAL PUBLIC LICENSE VERSION v2.1 AS PUBLISHED BY THE FREE SOFTWARE FOUNDATION.

REMOTE PAGE PROVIDER FOR EPISERVER CMS IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, BUT WITHOUT ANY WARRANTY; WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. SEE THE GNU LESSER GENERAL PUBLIC LICENSE FOR MORE DETAILS.
YOU SHOULD HAVE RECEIVED A COPY OF THE GNU LESSER GENERAL PUBLIC LICENSE ALONG WITH REMOTE PAGE PROVIDER FOR EPISERVER CMS]. IF NOT, SEE <HTTP://WWW.GNU.ORG/LICENSES/>.
*/
using System;
using System.Collections.Generic;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
using EPiServer.DataAbstraction;
using EPiServer.Security;
using EPiServer.Filters;
using EPiServer.DataAccess;
using System.Globalization;
using System.Security.Principal;
using System.Web.Security;

using EPiServer.Web;
using EPiServer.Core;


namespace EPiServer.Labs.RemoteProvider
{
    /// <summary>
    /// 
    /// </summary>
    [ServiceBehavior(ConcurrencyMode=ConcurrencyMode.Multiple,  InstanceContextMode=InstanceContextMode.Single)]
    public partial class RemoteProviderServer : IRemoteProvider, IDisposable
    {
        RemoteProviderDataManager _RemoteProviderDataManager;

        /// <summary>
        /// Initializes a new instance of the <see cref="RemoteProviderServer"/> class.
        /// </summary>
        public RemoteProviderServer()
        {
            _RemoteProviderDataManager = new RemoteProviderDataManager();
        }

        /// <summary>
        /// Handles the Closed event of the Channel control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        void Channel_Closed(object sender, EventArgs e)
        {
            _RemoteProviderDataManager.Remove(((IServiceChannel)sender).SessionId);
        }

        #region IRemoteProvider
        /// <summary>
        /// Initializes the specified initialize data.
        /// </summary>
        /// <param name="initializeData">The initialize data.</param>
        public void Initialize(ref InitializeData initializeData)
        {
            try
            {
                if (OperationContext.Current != null)
                {
                    OperationContext.Current.Channel.Closed += new EventHandler(Channel_Closed);
                }

                initializeData.RemoteSiteId = Configuration.Settings.Instance.Parent.SiteId;
                initializeData.RemoteSiteSecret = SiteSecurity.SiteSecret;
                initializeData.WasteBasketID = Configuration.Settings.Instance.PageWastebasketId;
                RemoteProviderData RemoteProviderData = new RemoteProviderData(initializeData);
                RemoteVirtualPathProvidersInfo remoteVirtualPathProvidersInfo = new RemoteVirtualPathProvidersInfo();
                InitializeVirtualPathProivder(ref remoteVirtualPathProvidersInfo);
                RemoteProviderData.InitializeData.RemoteVirtualPathProvidersInfo = remoteVirtualPathProvidersInfo;

                _RemoteProviderDataManager.Add(OperationContext.Current, RemoteProviderData);
            }
            catch (Exception ex)
            {
                HandleApplicationException(ex);
            }
        }

        /// <summary>
        /// Gets the children references.
        /// </summary>
        /// <param name="pageLink">The page link.</param>
        /// <returns>
        /// PageReferenceCollection which is children's pagereference
        /// </returns>
        public PageReferenceCollection GetChildrenReferences(PageReference pageLink)
        {
            try
            {
                Init(pageLink);
                PageDataCollection pages = DataFactory.Instance.GetChildren(pageLink);
                PageReferenceCollection pageRefs = new PageReferenceCollection(pages.Count);
                foreach (PageData page in pages)
                {
                    pageRefs.Add(page.PageLink);
                }
                return pageRefs;
            }
            catch (Exception ex)
            {
                HandleApplicationException(ex);
            }
            return  null;
        }

        /// <summary>
        /// Gets the page.
        /// </summary>
        /// <param name="pageLink">The page link.</param>
        /// <param name="selector">The selector.</param>
        /// <returns>
        /// RemotePageRaw which is a light version of PageData raw data
        /// </returns>
        public RemotePageRaw GetPage(PageReference pageLink, LanguageSelector selector)
        {
            try
            {
                Init(pageLink);
                PageData pageData = DataFactory.Instance.GetPage(pageLink, selector);
                return PackData(pageData);
            }
            catch (Exception ex)
            {
                HandleApplicationException(ex);
            }
            return null;
        }

        /// <summary>
        /// Gets the pages.
        /// </summary>
        /// <param name="pageLinks">The page links.</param>
        /// <param name="selector">The selector.</param>
        /// <returns>
        /// RemotePageRaw[] which is an array of light version of PageData raw data
        /// </returns>
        public RemotePageRaw[] GetPages(PageReferenceCollection pageLinks, LanguageSelector selector)
        {
            try
            {
                Init(pageLinks);

                List<RemotePageRaw> remotePagesRaw = new List<RemotePageRaw>(pageLinks.Count);

                foreach (PageReference pageLink in pageLinks)
                {
                    PageData pageData = DataFactory.Instance.GetPage(pageLink, selector);
                    remotePagesRaw.Add(PackData(pageData));
                }
                return remotePagesRaw.ToArray();
            }
            catch (Exception ex)
            {
                HandleApplicationException(ex);
            }
            return null;
        }
        /// <summary>
        /// Get collection of references to these pages
        /// </summary>
        /// <param name="pageLinks"></param>
        /// <returns></returns>
        public PageReferenceCollection GetReferencesToPages(PageReferenceCollection pageLinks)
        {
            try
            { 
                Init(pageLinks);
                return DataFactory.Instance.ProviderMap.GetDefaultPageProvider().GetReferencesToPages(pageLinks);
            }
            catch(Exception ex)
            {
                HandleApplicationException(ex);
            }
            return null;
        }


        /// <summary>
        /// Finds the pages with criteria.
        /// </summary>
        /// <param name="pageLink">The page link.</param>
        /// <param name="criterias">The criterias.</param>
        /// <param name="languageBranch">The language branch.</param>
        /// <param name="selector">The selector.</param>
        /// <returns>
        /// RemotePageRaw which is a light version of PageData raw data
        /// </returns>
        public RemotePageRaw[] FindPagesWithCriteria(PageReference pageLink, PropertyCriteriaCollection criterias, string languageBranch, LanguageSelector selector)
        {
            try
            {
                Init(pageLink);
                PageDataCollection pagesData = DataFactory.Instance.FindPagesWithCriteria(pageLink, criterias, languageBranch, selector);
                List<RemotePageRaw> remotePagesRaw = new List<RemotePageRaw>();

                foreach (PageData pageData in pagesData)
                {
                    remotePagesRaw.Add(PackData(pageData));
                }
                return remotePagesRaw.ToArray();
            }
            catch (Exception ex)
            {
                HandleApplicationException(ex);
            }
            return null;
        }

        /// <summary>
        /// Gets the language branches.
        /// </summary>
        /// <param name="pageLink">The page link.</param>
        /// <returns>
        /// RemotePageRaw which is a light version of PageData raw data
        /// </returns>
        public RemotePageRaw[] GetLanguageBranches(PageReference pageLink)
        {
            Init(pageLink);

            PageDataCollection pagesData = DataFactory.Instance.GetLanguageBranches(pageLink);
            List<RemotePageRaw> remotePagesRaw = new List<RemotePageRaw>();
            foreach (PageData pageData in pagesData)
            {
                remotePagesRaw.Add(PackData(pageData));
            }
            return remotePagesRaw.ToArray();
        }

        /// <summary>
        /// Creates the language branch.
        /// </summary>
        /// <param name="pageLink">The page link.</param>
        /// <param name="selector">The selector.</param>
        /// <returns>
        /// RemotePageRaw which is a light version of PageData raw data
        /// </returns>
        public RemotePageRaw CreateLanguageBranch(PageReference pageLink, LanguageSelector selector)
        {
            try
            {
                Init(pageLink);
                PageData pageData = DataFactory.Instance.CreateLanguageBranch(pageLink, selector);
                return PackData(pageData);
            }
            catch (Exception ex)
            {
                HandleApplicationException(ex);
            }
            return null;
        }


        /// <summary>
        /// Deletes the language branch.
        /// </summary>
        /// <param name="pageLink">The page link.</param>
        /// <param name="languageBranch">The language branch.</param>
        public void DeleteLanguageBranch(PageReference pageLink, string languageBranch)
        {
            try
            {
                Init(pageLink);
                DataFactory.Instance.DeleteLanguageBranch(pageLink, languageBranch);
            }
            catch (Exception ex)
            {
                HandleApplicationException(ex);
            }
        }

        /// <summary>
        /// Deletes the version.
        /// </summary>
        /// <param name="pageLink">The page link.</param>
        public void DeleteVersion(PageReference pageLink)
        {
            try
            {
                Init(pageLink);
                RemoteProviderContextManager.CallOperationWithSwitchContext(GetPrincipal().Principal,
                delegate()
                {
                    DataFactory.Instance.DeleteVersion(pageLink);
                });
            }
            catch (Exception ex)
            {
                HandleApplicationException(ex);
            }
        }

        /// <summary>
        /// Deletes the specified page link.
        /// </summary>
        /// <param name="pageLink">The page link.</param>
        /// <param name="forceDelete">if set to <c>true</c> [force delete].</param>
        public void Delete(PageReference pageLink, bool forceDelete)
        {
            try
            {
                Init(pageLink);
                RemoteProviderContextManager.CallOperationWithSwitchContext(GetPrincipal().Principal,
                delegate()
                {
                    DataFactory.Instance.Delete(pageLink, forceDelete);
                });
            }
            catch (Exception ex)
            {
                HandleApplicationException(ex);
            }
        }

        /// <summary>
        /// Deletes the children.
        /// </summary>
        /// <param name="pageLink">The page link.</param>
        /// <param name="forceDelete">if set to <c>true</c> [force delete].</param>
        public void DeleteChildren(PageReference pageLink, bool forceDelete)
        {
            try
            {
                Init(pageLink);
                RemoteProviderContextManager.CallOperationWithSwitchContext(GetPrincipal().Principal,
                  delegate()
                  {
                      DataFactory.Instance.DeleteChildren(pageLink, forceDelete);
                  });
            }
            catch (Exception ex)
            {
                HandleApplicationException(ex);
            }
        }

        /// <summary>
        /// Moves the specified page link.
        /// </summary>
        /// <param name="pageLink">The page link.</param>
        /// <param name="destinationLink">The destination link.</param>
        public void Move(PageReference pageLink, PageReference destinationLink)
        {
            try
            {
                Init(pageLink);
                destinationLink.RemoteSite = null;
                RemoteProviderContextManager.CallOperationWithSwitchContext(GetPrincipal().Principal,
                    delegate()
                    {
                        DataFactory.Instance.Move(pageLink, destinationLink, AccessLevel.NoAccess, AccessLevel.NoAccess);
                    });
            }
            catch (Exception ex)
            {
                HandleApplicationException(ex);
            }
        }

        /// <summary>
        /// Moves to wastebasket.
        /// </summary>
        /// <param name="pageLink">The page link.</param>
        public void MoveToWastebasket(PageReference pageLink)
        {
            try
            {
                Init(pageLink);
                RemoteProviderContextManager.CallOperationWithSwitchContext(GetPrincipal().Principal,
                  delegate()
                  {
                      DataFactory.Instance.MoveToWastebasket(pageLink);
                  });
            }
            catch (Exception ex)
            {
                HandleApplicationException(ex);
            }
        }

        /// <summary>
        /// Copies the specified page link.
        /// </summary>
        /// <param name="pageLink">The page link.</param>
        /// <param name="destinationLink">The destination link.</param>
        /// <param name="publishOnDestination">if set to <c>true</c> [publish on destination].</param>
        /// <param name="allowThreading">if set to <c>true</c> [allow threading].</param>
        /// <returns></returns>
        public PageReference Copy(PageReference pageLink, PageReference destinationLink, bool publishOnDestination, bool allowThreading)
        {
            try
            {
                Init(pageLink);
                destinationLink.RemoteSite = null;
                PageReference pageRef = PageReference.EmptyReference;
                RemoteProviderContextManager.CallOperationWithSwitchContext(GetPrincipal().Principal,
                  delegate()
                  {
                      pageRef = DataFactory.Instance.Copy(pageLink, destinationLink, AccessLevel.NoAccess, AccessLevel.NoAccess, publishOnDestination, allowThreading);
                  });

                return pageRef;
            }
            catch (Exception ex)
            {
                HandleApplicationException(ex);
            }
            return null;
        }

        /// <summary>
        /// Saves the specified remote page data.
        /// </summary>
        /// <param name="remotePageData">The remote page data.</param>
        /// <param name="action">The action.</param>
        /// <param name="IsModified">Indicates if the page is modified</param>
        /// <returns></returns>
        public PageReference Save(RemotePageRaw remotePageData, int action, bool IsModified)
        {
            try
            {
                Init((PageReference)null);

                PageReference pageLink = new PageReference();
                RemotePageHelper.GetProperty<PageReference>(remotePageData.Data, "PageLink", ref pageLink,
                   (RemotePageHelper.Parse<PageReference>)delegate(string value, ref  PageReference tmpPageLink)
                    {
                        tmpPageLink = PageReference.Parse(value);
                    });

                PageReference pageParentLink = new PageReference();
                RemotePageHelper.GetProperty<PageReference>(remotePageData.Data, "PageParentLink", ref pageParentLink,
                    (RemotePageHelper.Parse<PageReference>)delegate(string value, ref PageReference tmpParentLink)
                    {
                        tmpParentLink = PageReference.Parse(value);
                    });

                string pageTypeName = string.Empty;
                RemotePageHelper.GetProperty<string>(remotePageData.Data, "PageTypeName", ref  pageTypeName,
                    (RemotePageHelper.Parse<string>)delegate(string value, ref string tmpPagetypeName)
                    {
                        tmpPagetypeName = Convert.ToString(value, CultureInfo.InvariantCulture);
                    });


                DateTime pageStopPublish = PropertyDate.MaxValue;
                RemotePageHelper.GetProperty<DateTime>(remotePageData.Data, "PageStopPublish", ref  pageStopPublish,
                    (RemotePageHelper.Parse<DateTime>)delegate(string value, ref DateTime tmpPageStopPublish)
                    {
                        if (value == string.Empty)
                        {
                            remotePageData.Data["PageStopPublish"] = pageStopPublish.ToString(CultureInfo.InvariantCulture);
                        }
                    });

                if (pageParentLink == PageReference.EmptyReference)
                {
                    throw new EPiServerException("Missing pagelink and parentlink");
                }

                // It seems it is a new page
                if (PageReference.IsNullOrEmpty(pageLink))
                {
                    remotePageData.ACL = DataFactory.Instance.GetPage(pageParentLink).ACL;
                }

                PageData pageData = new PageData(new PageAccessControlList(), new PropertyDataCollection());
                PageProviderBase defaultProvider = DataFactory.Instance.ProviderMap.GetDefaultPageProvider();
                defaultProvider.InitializePageData(pageData, "", pageTypeName, Guid.Empty, pageLink, pageParentLink, remotePageData.Languages);
                defaultProvider.SetPropertyValues(pageData, remotePageData.Data,
                            delegate(string propName, string porpType, string propValue)
                            {
                                string type;
                                if (remotePageData.Type.TryGetValue(propName, out type))
                                {
                                    return porpType.ToString() == type;
                                }
                                return false;
                            }, false);

                pageData.SetValue("PageTargetFrame", null);
                pageData.IsModified = IsModified; // Sets the modified flag, that indicates if data has been changed.

                PageReference pageRef = PageReference.EmptyReference;
                RemoteProviderContextManager.CallOperationWithSwitchContext(GetPrincipal().Principal,
                    delegate()
                    {
                        pageRef = DataFactory.Instance.Save(pageData, (SaveAction)Enum.ToObject(typeof(SaveAction),(int)action));
                    }
                );
                return pageRef;
            }
            catch (Exception exception)
            {
                HandleApplicationException(exception);
            }

            return PageReference.EmptyReference;
        }

        /// <summary>
        /// Resolves the page from page GUID.
        /// </summary>
        /// <param name="pageGuid">The page GUID.</param>
        /// <param name="pageLink">The page link.</param>
        /// <returns>PageTypeName</returns>
        public string ResolvePageFromPageGuid(Guid pageGuid, out PageReference pageLink)
        {
            pageLink = PageReference.EmptyReference;
            try
            {
                Init((PageReference)null);
                PermanentPageLinkMap pplm = PermanentLinkMapStore.Find(pageGuid) as PermanentPageLinkMap;
                if (pplm != null)
                {
                    pageLink = pplm.PageReference;
                    PageCoreData pageCoreData = PageCoreData.Load(pageLink);
                    if (pageCoreData != null)
                    {
                        PageType pageType = PageType.Load(pageCoreData.PageTypeID);
                        if (pageType != null)
                        {
                            return pageType.Name;
                        }
                    }
                }
                return string.Empty;
            }
            catch (Exception ex)
            {
                HandleApplicationException(ex);
            }
            return string.Empty;
        }


        /// <summary>
        /// Resolves the page from page link.
        /// </summary>
        /// <param name="pageLink">The page link.</param>
        /// <param name="pageGuid">The page GUID.</param>
        /// <returns>PageTypeName</returns>
        public string ResolvePageFromPageLink(PageReference pageLink, out Guid pageGuid)
        {
            pageGuid = Guid.Empty;
            try
            {
                Init(pageLink);
                PermanentPageLinkMap pplm = PermanentLinkMapStore.Find(pageLink) as PermanentPageLinkMap;
                if (pplm != null)
                {
                    pageGuid = pplm.Guid;
                    PageCoreData pageCoreData = PageCoreData.Load(pageLink);
                    if (pageCoreData != null)
                    {
                        PageType pageType = PageType.Load(pageCoreData.PageTypeID);
                        if (pageType != null)
                        {
                            return pageType.Name;
                        }
                    }
                }
                return string.Empty;
            }
            catch (Exception ex)
            {
                HandleApplicationException(ex);
            }
            return string.Empty;
        }


        /// <summary>
        /// Lists the versions.
        /// </summary>
        /// <param name="pageLink">The page link.</param>
        /// <param name="languageBranch">The language branch.</param>
        /// <returns></returns>
        public PageVersionCollection ListVersions(PageReference pageLink, string languageBranch)
        {
            try
            {
            Init(pageLink);
            if (String.IsNullOrEmpty(languageBranch))
            {
                return DataFactory.Instance.ListVersions(pageLink);
            }
            else
            {
                return DataFactory.Instance.ListVersions(pageLink, languageBranch);
            }
            }
            catch (Exception ex)
            {
                HandleApplicationException(ex);
            }
            return null;
        }

        /// <summary>
        /// Lists the published versions.
        /// </summary>
        /// <param name="pageLink">The page link.</param>
        /// <returns></returns>
        public PageVersionCollection ListPublishedVersions(PageReference pageLink)
        {
            try
            {
            Init(pageLink);
            return DataFactory.Instance.ListPublishedVersions(pageLink);
            }
            catch (Exception ex)
            {
                HandleApplicationException(ex);
            }
            return null;
        }

        /// <summary>
        /// Loads the version.
        /// </summary>
        /// <param name="pageLink">The page link.</param>
        /// <returns></returns>
        public PageVersion LoadVersion(PageReference pageLink)
        {
            try
            {
            Init(pageLink);
            return DataFactory.Instance.LoadVersion(pageLink);
            }
            catch (Exception ex)
            {
                HandleApplicationException(ex);
            }
            return null;
        }

        /// <summary>
        /// Loads the published version.
        /// </summary>
        /// <param name="pageLink">The page link.</param>
        /// <param name="languageBranch">The language branch.</param>
        /// <returns></returns>
        public PageVersion LoadPublishedVersion(PageReference pageLink, string languageBranch)
        {
            try
            {
                Init(pageLink);
                if (String.IsNullOrEmpty(languageBranch))
                {
                    return DataFactory.Instance.LoadPublishedVersion(pageLink);
                }
                else
                {
                    return DataFactory.Instance.LoadPublishedVersion(pageLink, languageBranch);
                }
            }
            catch (Exception ex)
            {
                HandleApplicationException(ex);
            }
            return null;
        }

        /// <summary>
        /// Lists the delayed publish.
        /// </summary>
        /// <returns></returns>
        public RemotePageRaw[] ListDelayedPublish()
        {
            try
            {
                Init((PageReference)null);
                PageDataCollection pages = DataFactory.Instance.ListDelayedPublish();
                List<RemotePageRaw> remotePagesRaw = new List<RemotePageRaw>();
                foreach (PageData page in pages)
                {
                    remotePagesRaw.Add(PackData(page));
                }
                return remotePagesRaw.ToArray();
            }
            catch (Exception ex)
            {
                HandleApplicationException(ex);
            }
            return null;
        }
        #endregion

        #region Help method

        /// <summary>
        /// Inits the specified page links.
        /// </summary>
        /// <param name="pageLinks">The page links.</param>
        private void Init(PageReferenceCollection pageLinks)
        {
            CheckServiceAccess(Permission.WebServiceAccess);
            foreach (PageReference pageLink in pageLinks)
            {
                pageLink.RemoteSite = null;
            }
        }

        /// <summary>
        /// Inits the specified page link.
        /// </summary>
        /// <param name="pageLink">The page link.</param>
        private void Init(PageReference pageLink)
        {
            CheckServiceAccess(Permission.WebServiceAccess);
            if (pageLink != null)
            {
                pageLink.RemoteSite = null;
            }
        }

        /// <summary>
        /// Packs the data.
        /// </summary>
        /// <param name="pageData">The page data.</param>
        /// <returns></returns>
        internal RemotePageRaw PackData(PageData pageData)
        {
            RemoteProviderData RemoteProviderData;
            _RemoteProviderDataManager.TryGetValue(OperationContext.Current, out RemoteProviderData);

            RemotePageRaw remotePageData  = new RemotePageRaw();
 
            remotePageData.Data = DataFactory.Instance.ProviderMap.GetDefaultPageProvider().GetPropertyValues(pageData, PageProviderBase.PropertyTypes.LanguageNeutral | PageProviderBase.PropertyTypes.MetaData);
            remotePageData.Type = RemotePageHelper.PackPageDataType(pageData);
            if ((RemoteProviderData.InitializeData.CapabilitySupported & PageProviderCapabilities.Security) == PageProviderCapabilities.Security)
            {
                remotePageData.ACL = pageData.ACL;
            }
            remotePageData.Languages = new List<string>(pageData.PageLanguages); 
            return remotePageData;
        }

        /// <summary>
        /// Gets the principal.
        /// </summary>
        /// <returns></returns>
        private  PrincipalInfo GetPrincipal()
        {
            RemoteProviderData RemoteProviderData;
            if (_RemoteProviderDataManager.TryGetValue(OperationContext.Current, out RemoteProviderData))
            {
                if (RemoteProviderData.PrincipalInfo != null)
                {
                    return RemoteProviderData.PrincipalInfo;
                }
            }
            return PrincipalInfo.Current;
        }

        /// <summary>
        /// Checks the service access.
        /// </summary>
        /// <param name="permission">The permission.</param>
        private void CheckServiceAccess(Permission permission)
        {
            PrincipalInfo principal = GetPrincipal();
            if (!principal.IsPermitted(permission))
            {
                HandleApplicationException(new AccessDeniedException());
            }
        }

        /// <summary>
        /// Handles the application exception.
        /// </summary>
        /// <param name="ex">The ex.</param>
        void HandleApplicationException(Exception ex)
        {
            if (ex is EPiServerException)
            {
                throw new FaultException<RemoteProviderFault>(new RemoteProviderFault(ex.Message), ex.Message);
            }
            throw ex;
        }
        #endregion

        #region IDisposable Members

        public void Dispose()
        {
        }

        #endregion


        public CommunicationState GetCommunicationState()
        {
            throw new NotImplementedException("This should be implemented on The Server Side");
        }

    }
}

